package name.cyrax.red.jpv.impl.funcs.core;

import name.cyrax.red.jpv.api.adapters.DoubleAdapter;
import name.cyrax.red.jpv.api.adapters.IntegerAdapter;
import name.cyrax.red.jpv.api.adapters.LongAdapter;
import name.cyrax.red.jpv.api.adapters.NumericAdapter;
import name.cyrax.red.jpv.api.funcs.BinaryFunctor;
import name.cyrax.red.jpv.api.funcs.FunctorImplementor;
import name.cyrax.red.jpv.api.funcs.FunctorInstance;
import name.cyrax.red.jpv.api.funcs.Invocable;
import name.cyrax.red.jpv.api.funcs.info.FunctorMethod;
import name.cyrax.red.jpv.api.funcs.info.ValueType;
import name.cyrax.red.jpv.impl.adapters.DoubleImpl;
import name.cyrax.red.jpv.impl.adapters.IntegerImpl;
import name.cyrax.red.jpv.impl.adapters.LongImpl;

public abstract class FunctorBinaryImpl extends FunctorAbstractBinaryImpl<NumericAdapter> 
	implements BinaryFunctor
{
	private static final Class<?> OP_METHOD_CLASS = FunctorBinaryImpl.class; 
	private static final FunctorMethod FUNC_METHOD_INTEGER = FunctorMethod.newMethod(OP_METHOD_CLASS,
		"opInteger", ValueType.INTEGER, ValueType.INTEGER, ValueType.INTEGER);
	private static final FunctorMethod FUNC_METHOD_LONG = FunctorMethod.newMethod(OP_METHOD_CLASS,
		"opLong", ValueType.LONG, ValueType.LONG, ValueType.LONG);
	private static final FunctorMethod FUNC_METHOD_DOUBLE = FunctorMethod.newMethod(OP_METHOD_CLASS,
		"opDouble", ValueType.DOUBLE, ValueType.DOUBLE, ValueType.DOUBLE);

	private final FunctorInstance funcInstanceInteger = getFunctorInstance(FUNC_METHOD_INTEGER);
	private final FunctorInstance funcInstanceLong = getFunctorInstance(FUNC_METHOD_LONG);
	private final FunctorInstance funcInstanceDouble = getFunctorInstance(FUNC_METHOD_DOUBLE);

	public abstract int opInteger(int v0, int v1);
	public int opInteger(int v0, long v1) {
		return opInteger(v0, (int) v1);
	}
	public int opInteger(int v0, double v1) {
		return opInteger(v0, (int) v1);
	}
	
	public long opLong(long v0, int v1) {
		return opLong(v0, (long) v1);
	}
	public long opLong(long v0, double v1)  {
		return opLong(v0, (long) v1);
	}
	public abstract long opLong(long v0, long v1);

	public double opDouble(double v0, int v1) {
		return opDouble(v0, (double) v1);
	}
	public double opDouble(double v0, long v1) {
		return opDouble(v0, (double) v1);
	}
	public abstract double opDouble(double v0, double v1);

	public int binarySize(NumericAdapter p0, NumericAdapter p1) {
		return p0.size();
	}		
	
	final IntegerAdapter newBinaryAdapter(final FunctorInstance fi, final IntegerAdapter p0, final IntegerAdapter p1) {
		return checkAdapterType(new IntegerImpl() {
			@Override public FunctorInstance getFunctorInstance() {
				return fi;
			}
			@Override public NumericAdapter getParam(int index) {
				return switchOf(index, p0, p1);
			}
			@Override public int size() { return binarySize(p0, p1); }
			@Override public int getInteger(int index) {
				return opInteger(p0.getInteger(index), p1.getInteger(index));
			}
		});
	}
	final IntegerAdapter newBinaryAdapter(final FunctorInstance fi, final IntegerAdapter p0, final LongAdapter p1) {
		return checkAdapterType(new IntegerImpl() {
			@Override public FunctorInstance getFunctorInstance() {
				return fi;
			}
			@Override public NumericAdapter getParam(int index) {
				return switchOf(index, p0, p1);
			}
			@Override public int size() { return binarySize(p0, p1); }
			@Override public int getInteger(int index) {
				return opInteger(p0.getInteger(index), p1.getLong(index));
			}
		});
	}
	final IntegerAdapter newBinaryAdapter(final FunctorInstance fi, final IntegerAdapter p0, final DoubleAdapter p1) {
		return checkAdapterType(new IntegerImpl() {
			@Override public FunctorInstance getFunctorInstance() {
				return fi;
			}
			@Override public NumericAdapter getParam(int index) {
				return switchOf(index, p0, p1);
			}
			@Override public int size() { return binarySize(p0, p1); }
			@Override public int getInteger(int index) {
				return opInteger(p0.getInteger(index), p1.getDouble(index));
			}
		});
	}

	final LongAdapter newBinaryAdapter(final FunctorInstance fi, final LongAdapter p0, final IntegerAdapter p1) {
		return checkAdapterType(new LongImpl() {
			@Override public FunctorInstance getFunctorInstance() {
				return fi;
			}
			@Override public NumericAdapter getParam(int index) {
				return switchOf(index, p0, p1);
			}
			@Override public int size() { return binarySize(p0, p1); }
			@Override public long getLong(int index) {
				return opLong(p0.getLong(index), p1.getInteger(index));
			}
		});
	}
	final LongAdapter newBinaryAdapter(final FunctorInstance fi, final LongAdapter p0, final LongAdapter p1) {
		return checkAdapterType(new LongImpl() {
			@Override public FunctorInstance getFunctorInstance() {
				return fi;
			}
			@Override public NumericAdapter getParam(int index) {
				return switchOf(index, p0, p1);
			}
			@Override public int size() { return binarySize(p0, p1); }
			@Override public long getLong(int index) {
				return opLong(p0.getLong(index), p1.getLong(index));
			}
		});
	}
	final LongAdapter newBinaryAdapter(final FunctorInstance fi, final LongAdapter p0, final DoubleAdapter p1) {
		return checkAdapterType(new LongImpl() {
			@Override public FunctorInstance getFunctorInstance() {
				return fi;
			}
			@Override public NumericAdapter getParam(int index) {
				return switchOf(index, p0, p1);
			}
			@Override public int size() { return binarySize(p0, p1); }
			@Override public long getLong(int index) {
				return opLong(p0.getLong(index), p1.getDouble(index));
			}
		});
	}

	final DoubleAdapter newBinaryAdapter(final FunctorInstance fi, final DoubleAdapter p0, final IntegerAdapter p1) {
		return checkAdapterType(new DoubleImpl() {
			@Override public FunctorInstance getFunctorInstance() {
				return fi;
			}
			@Override public NumericAdapter getParam(int index) {
				return switchOf(index, p0, p1);
			}
			@Override public int size() { return binarySize(p0, p1); }
			@Override public double getDouble(int index) {
				return opDouble(p0.getDouble(index), p1.getInteger(index));
			}
		});
	}
	final DoubleAdapter newBinaryAdapter(final FunctorInstance fi, final DoubleAdapter p0, final LongAdapter p1) {
		return checkAdapterType(new DoubleImpl() {
			@Override public FunctorInstance getFunctorInstance() {
				return fi;
			}
			@Override public NumericAdapter getParam(int index) {
				return switchOf(index, p0, p1);
			}
			@Override public int size() { return binarySize(p0, p1); }
			@Override public double getDouble(int index) {
				return opDouble(p0.getDouble(index), p1.getLong(index));
			}
		});
	}
	final DoubleAdapter newBinaryAdapter(final FunctorInstance fi, final DoubleAdapter p0, final DoubleAdapter p1) {
		return checkAdapterType(new DoubleImpl() {
			@Override public FunctorInstance getFunctorInstance() {
				return fi;
			}
			@Override public NumericAdapter getParam(int index) {
				return switchOf(index, p0, p1);
			}
			@Override public int size() { return binarySize(p0, p1); }
			@Override public double getDouble(int index) {
				return opDouble(p0.getDouble(index), p1.getDouble(index));
			}
		});
	}
	
	@Override public FunctorImplementor<NumericAdapter> newAdapter(final IntegerAdapter p0) {
		return new FunctorImplementor<NumericAdapter>() {
			@Override public IntegerAdapter newAdapter(final IntegerAdapter p1) {
				return newBinaryAdapter(funcInstanceInteger, p0, p1);
			}

			@Override public IntegerAdapter newAdapter(final LongAdapter p1) {
				return newBinaryAdapter(funcInstanceInteger, p0, p1);
			}

			@Override public IntegerAdapter newAdapter(final DoubleAdapter p1) {
				return newBinaryAdapter(funcInstanceInteger, p0, p1);
			}
		};
	}
	@Override public FunctorImplementor<NumericAdapter> newAdapter(final LongAdapter p0) {
		return new FunctorImplementor<NumericAdapter>() {
			@Override public LongAdapter newAdapter(final IntegerAdapter p1) {
				return newBinaryAdapter(funcInstanceLong, p0, p1);
			}

			@Override public LongAdapter newAdapter(final LongAdapter p1) {
				return newBinaryAdapter(funcInstanceLong, p0, p1);
			}

			@Override public LongAdapter newAdapter(final DoubleAdapter p1) {
				return newBinaryAdapter(funcInstanceLong, p0, p1);
			}
		};
	}
	@Override public FunctorImplementor<NumericAdapter> newAdapter(final DoubleAdapter p0) {
		return new FunctorImplementor<NumericAdapter>() {
			@Override public DoubleAdapter newAdapter(final IntegerAdapter p1) {
				return newBinaryAdapter(funcInstanceDouble, p0, p1);
			}

			@Override public DoubleAdapter newAdapter(final LongAdapter p1) {
				return newBinaryAdapter(funcInstanceDouble, p0, p1);
			}

			@Override public DoubleAdapter newAdapter(final DoubleAdapter p1) {
				return newBinaryAdapter(funcInstanceDouble, p0, p1);
			}
		};
	}
	
	protected final <V extends Number> Invocable<V> newInvocable0(Invocable<V> v0, Invocable<?> v1)
	{
		return new BinaryInvocableImpl<V>(v0, v1); 
	}
	
	@Override public <V extends Number> Invocable<V> newInstance(Invocable<V> v0, Invocable<?> v1)
	{
		return newInvocable0(v0, v1);
	}
}
